#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

#include "ctree.h"

ctree_nleaf_t current;        /* entry to current command level   */
static ctree_nleaf_t root;    /* root of the command tree         */

/*
 * default_prompt - default function used to output current prompt
 */
static void default_prompt(void)
{
  assert(current);

  printf("%s> ", current->name?current->name:"");
}

/*
 * ctree_new_entry - init a new entry to a command level
 *
 * @name        name string of this entry
 * @prompt      prompt message callback of this entry
 *
 * return %NULL on error, new command level entry on success.
 */
ctree_nleaf_t ctree_new_entry(char *name, void (*prompt)(void))
{
  ctree_nleaf_t entry;
  
  entry = malloc(sizeof(*entry));
  if (!entry) {
    perror("malloc");
    return NULL;
  }

  bzero(entry, sizeof(*entry));

  entry->type   = CTREE_TYPE_NLEAF;
  entry->name   = name   ? name   : DEFAULT_NAME;
  entry->prompt = prompt ? prompt : default_prompt;
  
  return entry;
}

/*
 * ctree_new_cmd - init a new leaf for command container
 *
 * @name        name string of this command
 * @action      actions processed when exec this command
 *
 * return %NULL on error, new command container on success.
 */
ctree_leaf_t ctree_new_cmd(char *name, int (*action)(int, char**))
{
  ctree_leaf_t cmd;

  /* first some sanity check */
  if (!name) {
    fprintf(stderr, "failed to init new command: command name must "
	    "provided.\n");
    return NULL;
  }
  if (!action) {
    fprintf(stderr, "%s: failed to init new command, command action "
	    "must provided.\n", name);
    return NULL;
  }

  cmd = malloc(sizeof(*cmd));
  if (!cmd) {
    perror("malloc");
    return NULL;
  }

  bzero(cmd, sizeof(*cmd));

  cmd->type   = CTREE_TYPE_LEAF;
  cmd->name   = name;
  cmd->action = action;

  return cmd;
}

/*
 * ctree_regisger_node - add node to command tree.
 *
 * @father   command tree node to set as father
 * @node     node to be added to command tree
 */
void ctree_register_node(ctree_node_t father, ctree_node_t node)
{
  assert(node);
  
  node->father = father ? father : root;
  
  if (node->father->child) {
    /* sanity check first */
    ctree_node_t tmp = node->father->child;
    do {
      if (strcmp(node->name, tmp->name) == 0) {
	fprintf(stderr, "error: register an exist command: %s\n", node->name);
	return;
      }
      tmp = tmp->rsibling;
    } while (tmp != node->father->child);
    
    node->rsibling = node->father->child;
    node->lsibling = node->father->child->lsibling;
    node->rsibling->lsibling = node;
    node->lsibling->rsibling = node;
  } else {
    node->father->child = node;
    node->rsibling = node->lsibling = node;
  }
}

/*
 * ctree_get_node - get command tree node by name in desired tree level
 *
 * @name  name of the node we are looking for
 *
 * return %NULL on failed, corresponding command tree node on success.
 */
ctree_node_t ctree_get_node(ctree_nleaf_t entry, char *name)
{
  ctree_node_t node;

  if (!entry) {
    entry = root;
  }

  node = entry->child;
  
  if (!node) {
    return NULL;
  }

  /* look through children list */
  do {
    if (strcmp(name, node->name) == 0) {
      return node;
    }

    node = node->rsibling;
  } while (node != entry->child);

  return NULL;
}


/*
 * ctree_init - initialize command tree
 *
 * @name     name string of command tree root.
 * @prompt   prompt message callback of top level commands.
 *
 * return -1 on error, 0 on success.
 */
int ctree_init(char *name, void (*prompt)(void))
{
  assert(!root);

  current = root = ctree_new_entry(name, prompt);

  return root?0:-1;
}

/*
 * ctree_free_node - free a command tree node and all its children
 *
 * @node   command tree node to be freed. caller must guarantee it's not %NULL.
 */
static void ctree_free_node(ctree_node_t node)
{
  ctree_node_t node1, node2;

  if (!node->child) {
    free(node);
    return;
  }

  for (node1=node->child, node2=node1->rsibling;
       node2 != node->child;
       node1=node2, node2=node1->rsibling) {
    ctree_free_node(node1);
  }

  ctree_free_node(node1);
  free(node);
}
  
  
/*
 * cli_fini - deinitialize cli engine
 */
void ctree_fini(void)
{
  assert(root);
  
  ctree_free_node(root);
}
